package com.ruoyi.system.service.impl;

import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.system.service.ISysSignService;
import com.ruoyi.common.core.utils.DateUtils;
import org.apache.poi.ss.usermodel.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.SECONDS;


/**
 * 签到 业务层处理
 * https://cloud.tencent.com/developer/article/2056469
 * https://xie.infoq.cn/article/f2afca826b0a2c2861df4e2ef
 * 签到
 * @author ruoyi
 */
@Service
public class SysSignServiceImpl implements ISysSignService {

    @Autowired
    private RedisService redisService;

    /**
     * 用户签到，可以补签
     *
     * @param userId  用户ID
     * @param dateStr 查询的日期，默认当天 yyyy-MM-dd
     * @return 连续签到次数和总签到次数
     */
    public Map<String, Object> doSign(Long userId, String dateStr) {
        Map<String, Object> result = new HashMap<>();
        // 获取日期
        Date date = DateUtils.getDate(dateStr);
        // 获取日期对应的天数，多少号
        //int day = DateUtils.dayOfYear(date) - 1; // 从 0 开始
        int day = DateUtils.dayOfMonth(date) - 1; // 从 0 开始
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        // 签到
        redisService.setBit(signKey, day, true); //第一次setbit 返回0(false)

        String monthSign = monthSigned(userId, dateStr);

        // 根据当前日期统计签到次数
        Date today = new Date();
        String signCountKey = buildSignCountKey(userId, date);
        // 统计连续签到次数
        Long continuous = redisService.increment(signCountKey, 1);
        Long expireSeconds = DateUtils.getNowToNextDaysSeconds(2);
        redisService.expire(signCountKey, expireSeconds);
        //int continuous = getContinuousSignCount(userId, today);
        // 统计总签到次数
        //long count = getSumSignCount(userId, today);
        //result.put("message", "签到成功");
        result.put("continuous", continuous); //连续签到天数可以存redis，过期时间为后天零点；存在修改自增，不存在
        //result.put("count", count);

        return result;
    }

    /**
     * 是否签到
     *
     * @param userId
     * @param dateStr
     * @return
     */
    public boolean isSigned(Long userId, String dateStr) {
        // 获取日期
        Date date = DateUtils.getDate(dateStr);
        // 获取日期对应的天数，多少号
        //int day = DateUtils.dayOfYear(date) - 1; // 从 0 开始
        int day = DateUtils.dayOfMonth(date) - 1; // 从 0 开始
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        return redisService.getBit(signKey, day);
    }

    /**
     * 获取月份签到情况，按照月份是可以的，按照年份获取有问题
     * @param userId
     * @param dateStr
     * @return
     */
    public String monthSigned(Long userId, String dateStr) {
        // 获取日期
        Date date = DateUtils.getDate(dateStr);
        String signKey = buildSignKey(userId, date);
        // 获取日期对应的天数，多少号，假设是 31
        int dayOfMonth = DateUtils.dayOfMonth(date);
        //int dayOfMonth = DateUtils.dayOfYear(date);
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                .valueAt(0);
        // 获取月份datOfMonth到1号的所有签到状态
        // （也就是：如果签到情况为003，则显示3；签到情况为1003，则显示1003）
        List<Long> list = redisService.getBitField(signKey, bitFieldSubCommands);
        String total = Long.toBinaryString(list.get(0));
        return total;
    }

    /**
     * 统计连续签到次数
     *
     * @param userId 用户ID
     * @param date   查询的日期
     * @return
     */
    private int getContinuousSignCount(Long userId, Date date) {
        // 获取日期对应的天数，多少号，假设是 31
        int dayOfMonth = DateUtils.dayOfMonth(date);
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        // e.g. bitfield user:sign:5:202103 u31 0
        BitFieldSubCommands bitFieldSubCommands =
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                        .valueAt(0);
        // 获取用户从当前日期开始到 1 号的所有签到状态
        List<Long> list = redisService.getBitField(signKey, bitFieldSubCommands);
        if (list == null || list.isEmpty()) {
            return 0;
        }
        // 连续签到计数器
        int signCount = 0;
        long v = list.get(0) == null ? 0 : list.get(0);
        // 位移计算连续签到次数
        for (int i = dayOfMonth; i > 0; i--) {// i 表示位移操作次数
            // 右移再左移，如果等于自己说明最低位是 0，表示未签到
            if (v >> 1 << 1 == v) {
                // 用户可能当前还未签到，所以要排除是否是当天的可能性
                // 低位 0 且非当天说明连续签到中断了
                if (i != dayOfMonth) break;
            } else {
                // 右移再左移，如果不等于自己说明最低位是 1，表示签到
                signCount++;
            }
            // 右移一位并重新赋值，相当于把最低位丢弃一位然后重新计算
            v >>= 1;
        }
        return signCount;
    }

    /**
     * 统计总签到次数
     *
     * @param userId 用户ID
     * @param date   查询的日期
     * @return
     */
    private Long getSumSignCount(Long userId, Date date) {
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        // e.g. BITCOUNT user:sign:5:202103
        return redisService.getBitCount(signKey);
    }

    /**
     * 构建 Redis Key - user:sign:userId:yyyy
     *
     * @param userId 用户ID
     * @param date   日期
     * @return
     */
    private String buildSignKey(Long userId, Date date) {
        return String.format("user_daily_sign:%s:%s", userId,
                DateUtils.format(date, "yyyyMM"));
    }

    /**
     * 构建 Redis Key - user:sign:userId:yyyy
     *
     * @param userId 用户ID
     * @param date   日期
     * @return
     */
    private String buildSignCountKey(Long userId, Date date) {
        return String.format("user_daily_sign:%s:%sCount", userId,
                DateUtils.format(date, "yyyyMM"));
    }


    /**
     * https://blog.csdn.net/qq_48721706/article/details/121807985
     * 自定义线程池：否则spring会使用默认线程池
     * (@Async究竟用哪个线程池)https://blog.csdn.net/weixin_37949513/article/details/119763862
     * //https://mp.weixin.qq.com/s/r8GiCGT9TcPzNNbYMbKfrQ async 不要直接用@Async 默认线程池，使用自定义线程池
     * @return
     */
    @Async // 注意此处加上了此注解
    public Future<String> asyncReturn() {
        System.out.println("当前线程：" + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2); // 休眠 1s
            //TimeUnit.DAYS.sleep(1); // 休眠 1 天
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new AsyncResult<String>( "service hello");
    }

    @Async
    public void asyncVoid() {
        System.out.println("当前线程：" + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2); // 休眠 1s
            //TimeUnit.DAYS.sleep(1); // 休眠 1 天
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
